﻿//
// Created by @author 吴子昂 on 2025/11/4
//

#include "recognizer.h"

recognizer::recognizer()
= default;

recognizer::~recognizer()
= default;

/**
* 筛选并且保存最大的N个轮廓
*
* @param contours 待排序的一组轮廓
* @param keepN 选取的最大的几个轮廓（默认为 2 个）
* @param minArea 过滤小面积轮廓的阈值（默认为 100）
* @return 从大到小排序好的轮廓
* */
vector<vector<Point>> recognizer::filterContours(const vector<vector<Point>>& contours, int keepN = 2, double minArea = 100.0) {
    vector<vector<Point>> result;

    // 将轮廓按照面积从大到小排序
    vector<vector<Point>> areaIdx;
    for (const auto & contour : contours) {
        double area = contourArea(contour);
        // 过滤面积过小的轮廓
        if (area > minArea) {
            areaIdx.emplace_back(contour);
        }
    }

    // 降序排序
    sort(areaIdx.begin(), areaIdx.end(),
        [](const vector<Point>& a, const vector<Point>& b)
        {
            return contourArea(a) > contourArea(b);
        });

    // 保留前N个轮廓
    int take = min(keepN, (int)areaIdx.size());
    for (int i = 0; i < take; i++) {
        result.emplace_back(areaIdx[i]);
    }
    return result;
}

/**
* 获取最小外接矩形
*
* @param contours 待识别外接矩形的轮廓
* @return 外接矩形的所有点的点集
* */
Rect recognizer::getCommonMinRect(const vector<vector<Point>>& contours)
{
    vector<Point> allPts;

    // 合并所有轮廓的所有点
    for (const auto& contour : contours)
    {
        allPts.insert(allPts.end(), contour.begin(), contour.end());
    }

    return boundingRect(allPts);
}

/**
 *对点集进行凸包检测以及多边形拟合，检测最小外接四边形
 *
 * @param points 输入的要进行检测最小四边形的点集
 * @return 检测结果
 */
vector<Point> recognizer::getConvexHullQuadrilateral(const vector<Point>& points)
{
    // 至少需要4个点才能形成四边形
    if (points.size() < 4) {
        return {};
    }

    // 计算点集的凸包
    vector<Point> hull;
    convexHull(points, hull);
    // 过滤过小的凸包
    if (hull.size() < 4) {
        return {};
    }

    // 计算凸包周长，用于设置逼近精度
    double hullPerimeter = arcLength(hull, true);

    // 多边形逼近：将凸包简化为四边形
    vector<Point> approxQuad;
    approxPolyDP(hull, approxQuad, 0.045 * hullPerimeter, true);

    // 确保逼近结果是4个顶点的四边形
    if (approxQuad.size() == 4) {
        return approxQuad;
    } else {
        return {};
    }
}

/**
* 获取装甲的最小框选矩形
*
* @param image 已经预处理好的图像
* @param frame 需要绘制在上面的图像
* @note 如果出现错误，会 return 0 终止函数
* */
int recognizer::getStructure(const Mat& image, Mat frame)
{
    // 查找轮廓
    vector<vector<Point>> contours;
    findContours(image, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE); // 只找外轮廓

    // 过滤轮廓（保留面积最大的2个）
    vector<vector<Point>> targetContours = filterContours(contours, 2, 500.0);
    if (targetContours.size() < 2) {
        return 0;
    }

    // 合并所有轮廓的点到一个点集中
    vector<Point> allPoints;
    for (const auto& contour : targetContours) {
        // 合并点
        allPoints.insert(allPoints.end(), contour.begin(), contour.end());
    }

    // 检查合并后的点集是否为空（避免minAreaRect处理空集出错）
    if (allPoints.empty()) {
        return 0;
    }
    // 绘制两个目标的轮廓
    drawContours(frame, targetContours, 0, Scalar(0, 255, 0), 2);
    drawContours(frame, targetContours, 1, Scalar(255, 0, 0), 2);

    vector<Point> hull = getConvexHullQuadrilateral(allPoints);
    if (hull.empty())
    {
        cout<< "无法识别"<<endl;
        return 0;
    }

    for (int i = 0; i < 4; ++i) {
        line(frame, hull[i], hull[(i + 1) % 4], Scalar(0, 0, 255), 2);
    }

    return 0;
}
